#include <pwd.h>
#include <grp.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <fcntl.h>
#include <stdarg.h>
#include <dlfcn.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/param.h>

#include <exception>
#include <vector>
#include <string>
#include <map>

#include <fcgi_stdio.h>
#include <boost/scoped_array.hpp>

#include <v8.h>

#include "counterpartOpenDirectory.h"
#include "counterpartFormatString.h"

using namespace v8;
using namespace std;
using namespace boost;

static const char kPNS = '/';

namespace Exceptions
{
	DeclareSimpleException (LongRead);
	DeclareSimpleException (ShortRead);
	DeclareSimpleException (UserNotFound);
	DeclareSimpleException (GroupNotFound);
	DeclareSimpleException (SecurityAnomaly);
	DeclareSimpleException (UnexpectedProcess);
	DeclareSimpleException (CannotAcceptRequest);
	DeclareSimpleException (BootstrapScriptDied);
	DeclareSimpleException (MustStartAsSuperUser);
	DeclareSimpleException (BootstrapScriptInsane);
	DeclareSimpleException (BootstrapScriptFailed);
	DeclareSimpleException (ScriptFileInaccessible);
	DeclareSimpleException (GarbageCollectionFailed);
	DeclareSimpleException (CouldNotClearEnvironment);
	DeclareSimpleException (CannotCloseDynamicLibrary);
	DeclareSimpleException (MalformedEnvironmentVariable);
	DeclareSimpleException (CouldNotSetEnvironmentVariable);

	static const char kStringScopeBootstrap [ ] = "native";
}

static void SetPersona (uid_t userID, gid_t groupID)
{
	errno = 0;
	struct passwd const * userInfo (getpwuid (userID));

	if (!userInfo)
	{
		if (errno) Throw (Exceptions::Errno ( ));
		Throw (Exceptions::UserNotFound ( ));
	}

	errno = 0;
	struct group const * groupInfo (getgrgid (userInfo->pw_gid));

	if (!groupInfo)
	{
		if (errno) Throw (Exceptions::Errno ( ));
		Throw (Exceptions::GroupNotFound ( ));
	}

	if (setgid (groupID))
	{
		syslog (LOG_ERR, "could not set effective group ID to %d", groupID);
		Throw (Exceptions::Errno ( ));
	}

	if (initgroups (userInfo->pw_name, userInfo->pw_gid))
	{
		syslog (LOG_ERR, "could not init groups including ID %d", userInfo->pw_gid);
		Throw (Exceptions::Errno ( ));
	}

	if (setuid (userID))
	{
		syslog (LOG_ERR, "could not set effective user ID to %d", userID);
		Throw (Exceptions::Errno ( ));
	}

	if (setenv ("USER",userInfo->pw_name,true))
	{
		Throw (Exceptions::CouldNotSetEnvironmentVariable ( ));
	}

	if (setenv ("LOGNAME",userInfo->pw_name,true))
	{
		Throw (Exceptions::CouldNotSetEnvironmentVariable ( ));
	}

	if (setenv ("HOME",userInfo->pw_dir,true))
	{
		Throw (Exceptions::CouldNotSetEnvironmentVariable ( ));
	}

	if (setenv ("SHELL",userInfo->pw_shell,true))
	{
		Throw (Exceptions::CouldNotSetEnvironmentVariable ( ));
	}

	if (setenv ("GROUP",groupInfo->gr_name,true))
	{
		Throw (Exceptions::CouldNotSetEnvironmentVariable ( ));
	}

	if (setenv ("PWD",userInfo->pw_dir,true))
	{
		Throw (Exceptions::CouldNotSetEnvironmentVariable ( ));
	}

	if (chdir (userInfo->pw_dir))
	{
		Throw (Exceptions::Errno ( ));
	}
}

static off_t SetPersonaPerFile (const char * path)
{
	struct stat statBuf;
	if (stat (path, &statBuf)) Throw (Exceptions::Errno ( ));

	if (geteuid ( ) != statBuf.st_uid) // file owned by someone other than current user?
	{
		SetPersona (statBuf.st_uid, statBuf.st_gid);
	}

	return statBuf.st_size;
}

/*
static void LogUserAndGroupID (const char * when)
{
	syslog (LOG_NOTICE, "persona %s, user %d group %d", when, geteuid ( ), getegid ( ));
}
*/

static void ReadFileIntoBuffer (const char * pathName, void * buf, size_t count)
{
	//	We probably ought to be using mmap, but for now I like the fact that
	//	all these steps have diagnostic value.

	if (count > SSIZE_MAX)
	{
		syslog (LOG_ERR, "the read system call can't handle %u bytes", count);
		Throw (Exceptions::LongRead ( ));
	}

	int fd (open (pathName, O_RDONLY | O_NOCTTY));

	if (-1 == fd)
	{
		int e (errno);
		syslog (LOG_ERR, "errno %d prevented opening file %s", e, pathName);
		Throw (Exceptions::Errno (e));
	}

	ssize_t bytesRead (read (fd, buf, count));

	if (bytesRead < 0)
	{
		int e (errno);
		syslog (LOG_ERR, "errno %d prevented reading file %s", e, pathName);
		if (-1 == close (fd)) Throw (Exceptions::Errno ( ));
		Throw (Exceptions::Errno (e));
	}

	assert (sizeof (bytesRead) <= sizeof (count));

	if (size_t (bytesRead) < count)
	{
		syslog (LOG_ERR, "only %d of %d bytes could be read from file %s", bytesRead, count, pathName);
		if (-1 == close (fd)) Throw (Exceptions::Errno ( ));
		Throw (Exceptions::ShortRead ( ));
	}

	if (-1 == close (fd)) Throw (Exceptions::Errno ( ));
}

static off_t FileIsMinimallyAccessible (const char * path)
{
	struct stat statBuf;
	if (stat (path, &statBuf)) return -1;
	if (S_ISREG (statBuf.st_mode)) return statBuf.st_size;
	return 0;
}

static Local <Value> ReadCompileRunScript (const char * scriptFileName, off_t scriptSize = -1)
{
	syslog (LOG_NOTICE, "ReadCompileRunScript passed '%s'", scriptFileName);

	Local <Value> scriptResult;

	if (scriptSize < 0)
	{
		scriptSize = FileIsMinimallyAccessible (scriptFileName);
		if (-1 == scriptSize) Throw (Exceptions::ScriptFileInaccessible ( ));
	}

	scoped_array <char> scriptBuffer (new char [scriptSize]);
	ReadFileIntoBuffer (scriptFileName, scriptBuffer.get ( ), scriptSize);
	Local <String> scriptString (String::New (scriptBuffer.get ( ), scriptSize));
	scriptBuffer.reset ( ); // save some heap now that we have a UTF16 copy
	Local <String> scriptFileNameString (String::New (scriptFileName));
	
	TryCatch tryCatch;
	Local <Script> script (Script::Compile (scriptString, scriptFileNameString));
	
	if (script.IsEmpty ( ))
	{
		assert (tryCatch.HasCaught ( ));
		Local <Value> compileException (tryCatch.Exception ( ));
		String::Utf8Value compileExceptionStr (compileException);
		Local <Message> message (tryCatch.Message ( ));
	
		if (message.IsEmpty ( ))
		{
			syslog (LOG_NOTICE, "script '%s' could not be compiled due to \"%s\"",
				scriptFileName, *compileExceptionStr);
		}
		else
		{
			syslog (LOG_NOTICE, "script '%s' could not be compiled due to \"%s\" at offset %i of line %i",
				scriptFileName, *compileExceptionStr, message->GetStartColumn ( ), message->GetLineNumber ( ));
		}

		return scriptResult;
	}

	tryCatch.Reset ( );
	scriptResult = script->Run ( );
	syslog (LOG_NOTICE, "attempted to execute %s", scriptFileName);

	//	deal with the script's aftermath

	if (scriptResult.IsEmpty ( ))
	{
		assert (tryCatch.HasCaught ( ));
		Local <Value> executionException (tryCatch.Exception ( ));
		String::Utf8Value executionExceptionStr (executionException);
		Local <Message> message (tryCatch.Message ( ));

		if (message.IsEmpty ( ))
		{
			syslog (LOG_NOTICE, "uncaught exception \"%s\" while executing script '%s'",
				*executionExceptionStr, scriptFileName);
		}
		else
		{
			syslog (LOG_NOTICE, "uncaught exception \"%s\" while executing script '%s' at offset %i of line %i",
				*executionExceptionStr, scriptFileName, message->GetStartColumn ( ), message->GetLineNumber ( ));
		}

		return scriptResult;
	}

	String::Utf8Value valueString (scriptResult);
	syslog (LOG_NOTICE, "script returned: %s", *valueString);
	return scriptResult;
}

static void CollectGarbage (void)
{
	syslog (LOG_NOTICE, "collecting garbage");

	Local <Context>		currentContext	(Context::GetCurrent ( ));
	Local <Object>		global			(currentContext->Global ( ));
	Local <Value>		gc				(global->Get (String::New ("gc")));
	Local <Function>	gcFunc			(Function::Cast (*gc));
	TryCatch			tryCatch;
	Local <Value>		gcResult		(gcFunc->Call (global, 0, NULL));

	if (gcResult.IsEmpty ( ))
	{
		Throw (Exceptions::GarbageCollectionFailed ( ));

		//	We could do a lot more complicated stuff than what appears above,
		//	such as what's commented out below, but since I don't have a real
		//	world test-case for failure, I decided to keep this path as
		//	simple as possible.

// 		assert (tryCatch.HasCaught ( ));
// 		Local <Value> gcException (tryCatch.Exception ( ));
// 		String::Utf8Value gcExceptionStr (gcException);
// 		Local <Message> message (tryCatch.Message ( ));
// 	
// 		if (message.IsEmpty ( ))
// 		{
// 			syslog (LOG_NOTICE, "could not collect garbage because \"%s\"", *gcExceptionStr);
// 		}
// 		else
// 		{
// 			Handle <Value> message->GetScriptResourceName ( );
// 
// 			syslog (LOG_NOTICE, "script '%s' could not be compiled due to \"%s\" at offset %i of line %i",
// 				scriptFileName, *compileExceptionStr, message->GetStartColumn ( ), message->GetLineNumber ( ));
// 		}
	}
	else if (false == gcResult->IsUndefined ( ))
	{
		syslog (LOG_NOTICE, "gc result is not undefined");
	}

	syslog (LOG_NOTICE, "collected garbage");
}

void AcceptLoop (bool clone, bool setPersona)
{
	syslog (LOG_NOTICE, "AcceptLoop");

	unsigned long long invocationCount (0);

	while (FCGI_Accept ( ) >= 0)
	{
		syslog (LOG_NOTICE, "accepted connection %llu...", ++invocationCount);

		//	if we ain't got no script, there's no point in going any further

		const char * scriptFileName = getenv ("SCRIPT_FILENAME");

		if (!scriptFileName)
		{
			syslog (LOG_NOTICE, "no script name");
			continue;
		}

		off_t scriptSize = -1;

		if (clone)
		{
			//	Make a child process. This is for security rather than
			//	concurrency. We want to give up root privileges into a
			//	fresh clone, but because we need to play nice with the
			//	existing FastCGI mechanism we simply wait for the child
			//	to finish. This also happens to be a simple/convenient
			//	way to prevent the accumulation of zombies.
	
			pid_t childProcess (fork ( ));
			if (-1 == childProcess) Throw (Exceptions::Errno ( ));
	
			if (childProcess)
			{
				//	We are the parent. Simply wait for the child to
				//	finish and loop back around for another request.
	
				syslog (LOG_NOTICE, "parent of child %i will wait patiently", childProcess);

				int waitStatus;
				pid_t waitResult (waitpid (childProcess, &waitStatus, 0));
				if (-1 == waitResult) Throw (Exceptions::Errno ( ));
				if (waitResult != childProcess) Throw (Exceptions::UnexpectedProcess ( ));

				if (WIFEXITED (waitStatus))
				{
					syslog (LOG_NOTICE, "child %i exited normally with status %i", childProcess, WEXITSTATUS (waitStatus));
				}
				else if (WIFSIGNALED (waitStatus))
				{
					syslog (LOG_ERR, "child %i exited due to signal %i", childProcess, WTERMSIG (waitStatus));
				}
				else if (WIFSTOPPED (waitStatus))
				{
					syslog (LOG_ERR, "child %i stopped due to signal %i", childProcess, WSTOPSIG (waitStatus));
				}
	
				#if DEBUG
					break;
				#else
					continue;
				#endif
			}

			//	We are the child. We have a fresh clone of our
			//	JavaScript environment and can let the script go
			//	wild in it before tossing it.
		}

		if (setPersona)
		{
			//	"Become" the script file owner...
	
			scriptSize = SetPersonaPerFile (scriptFileName);
	
			//	Having become some other user, if we're able to
			//	become root again (which is possible under Linux
			//	if this code isn't perfect), then we have a very
			//	bad problem and we should not try to proceed. We
			//	hope this is merely paranoia, but if our
			//	assumption is that the script is hostile, better
			//	safe than sorry.
	
			if (geteuid ( ) && !seteuid (0))
			{
				syslog (LOG_ERR, "was able to become root again!");
				Throw (Exceptions::SecurityAnomaly ( ));
			}
		}

		//	Finally, run the blinkin' script already.

		(void) ReadCompileRunScript (scriptFileName, scriptSize);

		//	Before finishing HTTP, let's give all the weak objects
		//	a chance to get their callbacks. We do this mostly to
		//	enable them to prove they work, but I suppose it's
		//	possible some of them might actually need this.

		CollectGarbage ( );

		//	Because we are the child, we simply finish the
		//	transaction and break out of the loop.

		FCGI_Finish ( );
		break;
	}
}

void OpenLog (const char * pathToMe) throw ( )
{
	const char * lastSlash (strrchr (pathToMe, kPNS));
	const char * ident (lastSlash ? lastSlash + 1 : pathToMe);
	static const int options (LOG_CONS | LOG_PERROR | LOG_PID);
	openlog (ident, options, LOG_DAEMON);
	syslog (LOG_NOTICE, "starting");
}

static Handle <Value> log (const Arguments & arguments)
{
	//	As we are called by V8, which compiles with C++ exceptions
	//	turned off, it would be unfriendly to allow an exception
	//	to percolate upward.

	try
	{
		uint32_t argumentCount (arguments.Length ( ));
	
		if (argumentCount)
		{
			uint32_t argumentIndex (0);
	
			do
			{
				String::Utf8Value logString (arguments [argumentIndex]);
				syslog (LOG_INFO, "%s", *logString);
			}
			while (++argumentIndex < argumentCount);
		}
	}
	catch (exception & e)
	{
		ScheduleJavaScriptException (&e);
	}
	catch (...)
	{
		ScheduleJavaScriptException (NULL);
	}

	return Undefined ( );
}

static Handle <Value> loadScript (const Arguments & arguments)
{
	//	As we are called by V8, which compiles with C++ exceptions
	//	turned off, it would be unfriendly to allow an exception
	//	to percolate upward.

	try
	{
		uint32_t argumentCount (arguments.Length ( ));
	
		if (!argumentCount) // read from stdin
		{
			return Undefined ( ); // unimplemented for now
		}
	
		string result;
		uint32_t argumentIndex (0);
	
		do
		{
			Local <Value> pathName (arguments [argumentIndex]);
			Demand (pathName->IsString ( ));	
			String::Utf8Value pathNameStr (pathName);
			off_t fileSize (FileIsMinimallyAccessible (*pathNameStr));
			Demand (0 < fileSize);
			scoped_array <char> buffer (new char [fileSize]);
			ReadFileIntoBuffer (*pathNameStr, buffer.get ( ), fileSize);
			if (argumentCount == 1) return String::New (buffer.get ( ), fileSize);
			(void) result.append (buffer.get ( ), fileSize);
		}
		while (++argumentIndex < argumentCount);
	
		return String::New (result.data ( ), result.size ( ));
	}
	catch (exception & e)
	{
		ScheduleJavaScriptException (&e);
	}
	catch (...)
	{
		ScheduleJavaScriptException (NULL);
	}

	return Undefined ( );
}

static Handle <Value> loadBinary (const Arguments & arguments)
{
	//	As we are called by V8, which compiles with C++ exceptions
	//	turned off, it would be unfriendly to allow an exception
	//	to percolate upward.

	try
	{
		uint32_t argumentCount (arguments.Length ( ));

		if (argumentCount != 1)
		{
			FormatString errorString ("%s expected 1 argument and got %u", __FUNCTION__, argumentCount);
			Throw (Exceptions::ScopedMessage (Exceptions::kStringScopeBootstrap, errorString.c_str ( )));
		}

		Local <Value> pathName (arguments [0]);

		if (!pathName->IsString ( ))
		{
			FormatString errorString ("parameter to loadBinary was not a string");
			Throw (Exceptions::ScopedMessage (Exceptions::kStringScopeBootstrap, errorString.c_str ( )));
		}

		String::Utf8Value pathNameStr (pathName);
		off_t fileSize (FileIsMinimallyAccessible (*pathNameStr));

		if (0 > fileSize)
		{
			FormatString errorString ("binary file '%s' inaccessible", *pathNameStr);
			Throw (Exceptions::ScopedMessage (Exceptions::kStringScopeBootstrap, errorString.c_str ( )));
		}

		//	Note we make use of RTLD_DEEPBIND. At the time of this writing,
		//	Mac OS X doesn't support RTLD_DEEPBIND. We will probably need
		//	to replace this logic with calls to DYLD when we port. I can
		//	only hope the full DYLD API supports something which addresses
		//	the same sort of problem.

		void * libHandle = dlopen (*pathNameStr, RTLD_NOW | RTLD_DEEPBIND);

		if (!libHandle)
		{
			struct DynamicLink : public Exceptions::ScopedMessage
			{
				DynamicLink (void) : Exceptions::ScopedMessage ("dlerror", dlerror ( ))
				{
					// it was enough to init bases and members
				}
			};
			Throw (DynamicLink ( ));
		}

		try
		{
			typedef Local <Value> (* vInstantiate) (void);
	
			vInstantiate instantiate (vInstantiate (dlsym (libHandle, "instantiate")));
	
			if (!instantiate)
			{
				FormatString errorString ("binary '%s' didn't define symbol 'instantiate'", *pathNameStr);
				Throw (Exceptions::ScopedMessage (Exceptions::kStringScopeBootstrap, errorString.c_str ( )));
			}

			Local <Value> child (instantiate ( ));

			if (child.IsEmpty ( ))
			{
				FormatString errorString ("'instantiate' function in binary '%s' returned an empty handle", *pathNameStr);
				Throw (Exceptions::ScopedMessage (Exceptions::kStringScopeBootstrap, errorString.c_str ( )));
			}

			return child;
		}
		catch (...)
		{
			if (dlclose (libHandle)) Throw (Exceptions::CannotCloseDynamicLibrary ( ));
			throw;
		}

		if (dlclose (libHandle)) Throw (Exceptions::CannotCloseDynamicLibrary ( ));
	}
	catch (exception & e)
	{
		ScheduleJavaScriptException (&e);
	}
	catch (...)
	{
		ScheduleJavaScriptException (NULL);
	}

	return Undefined ( );
}

static bool PrepareExtensions (		const string &				filePath,
									const char *				parentName,
									Local <ObjectTemplate>		parent		)
{
	//	FIRST CHOICE: a script, since it's less work for a script to load a binary
	//	than the other way around.

	string extensionScriptPath (filePath + kPNS + "extension.js");
	off_t scriptSize (FileIsMinimallyAccessible (extensionScriptPath.c_str ( )));

	if (0 <= scriptSize)
	{

		parent->Set (	String::New (parentName),
						String::New (extensionScriptPath.data ( ), extensionScriptPath.size ( ))	);

		return true;
	}

	//	SECOND CHOICE: a binary. Obviously, we need "dll" instead of "so" on Windows,
	//	but that's the least of our problems there.

	string extensionBinaryPath (filePath + kPNS + "extension.so");

	if (0 <= FileIsMinimallyAccessible (extensionBinaryPath.c_str ( )))
	{
		parent->Set (	String::New (parentName),
						String::New (extensionBinaryPath.data ( ), extensionBinaryPath.size ( ))	);

		return true;
	}

	return false;
}

static bool PrepareExtensions (const string & filePath, Local <ObjectTemplate> parent)
{
	bool result = false;

	OpenDirectory openDir (filePath.c_str ( ));

	if (openDir)
	{
		while (struct dirent * dirEntry = openDir.NextEntry ( ))
		{
			if ('.' == dirEntry->d_name [0]) continue;
	
			if (DT_DIR == dirEntry->d_type)
			{
				string deeperPath (filePath + kPNS + dirEntry->d_name);

				if (PrepareExtensions (deeperPath, dirEntry->d_name, parent))
				{
					result = true;
				}
				else
				{
					Local <ObjectTemplate> child (ObjectTemplate::New( ));

					if (PrepareExtensions (deeperPath, child))
					{
						parent->Set (dirEntry->d_name, child);
						result = true;
					}
				}
			}
		}
	}

	return result;
}

static void PrepareExtensions (const char * pathToMe, Local <ObjectTemplate> & global)
{
	static const char extensionsName [ ] = "extensions";

	if (const char * lastSlash = strrchr (pathToMe, kPNS))
	{
		(void) PrepareExtensions (string (pathToMe, lastSlash - pathToMe + 1) + extensionsName, global);
	}
	else
	{
		//	The other case will always contain a slash, which affects the behavior
		//	of dlopen, so we add a slash in this case just to make sure the cases
		//	have the same behavior.

		(void) PrepareExtensions (string ("./") + extensionsName, global);
	}
}

struct FullPathToExecutable : public FormatString
{
	#define kFormat "/proc/%i/exe" // will differ per flavor of UNIX

	FullPathToExecutable (void) : FormatString (kFormat, getpid ( ))
	{
		char link [PATH_MAX + 1];
		ssize_t charsInTarget (readlink (c_str ( ), link, PATH_MAX));
		if (-1 == charsInTarget) Throw (Exceptions::Errno ( ));
		assign (link, charsInTarget);
	}

	#undef kFormat
};

struct RandomForShuffle
{
	//	There's no way to portably seed the random number generator
	//	used in the two-parameter version of random_shuffle, so we
	//	provide our own generator so we can seed it. This one is
	//	based on Josuttis.

	ptrdiff_t operator ( ) (ptrdiff_t max)
	{
		double temp (static_cast <double> (rand ( )) / static_cast <double> (RAND_MAX));
		return static_cast <ptrdiff_t> (temp * max + 0.5);
	}
};

static void CounterpartFatalErrorCallback (const char * location, const char * message)
{
	//	I would love to throw a C++ exception here.
	syslog (LOG_ERR, "V8 aborting: %s: %s", location, message);
	abort ( );
}

int main (void)
{
	try
	{
		FullPathToExecutable pathToMe;
		OpenLog (pathToMe.c_str ( ));

		//	We can't trust the environment we were given because suid root
		//	programs are targets of abuse.

		if (clearenv ( ))
		{
			syslog (LOG_ERR, "environment could not be cleared");
			Throw (Exceptions::CouldNotClearEnvironment ( ));
		}

		//	Since we expect to be running a long time and have no need
		//	of any particular working directory, set it to root so the
		//	sysadmin is more likely to be able to unmount the relevant
		//	file system.

		if (chdir ("/")) Throw (Exceptions::Errno ( ));

		//	Seed the standard psuedo-random number generator so it
		//	doesn't generate the same sequence on every run. I don't
		//	know if V8 uses it, but extensions might.

		srand (time (NULL));

		//	Set up V8 for all contexts.

		V8::SetFatalErrorHandler (CounterpartFatalErrorCallback);
		static const char v8Flags [ ] = "--expose-gc";
		V8::SetFlagsFromString (v8Flags, sizeof (v8Flags) - 1);

		HandleScope handleScope;
		Local <ObjectTemplate> global (ObjectTemplate::New( ));
		Local <ObjectTemplate> extensions (ObjectTemplate::New( ));

		//	Though we hang these functions off of the global object,
		//	the bootstrap script will move or delete them.

		global->Set ("extensions", extensions);

		global->Set ("log", FunctionTemplate::New (log));
		global->Set ("loadScript", FunctionTemplate::New (loadScript));
		global->Set ("loadBinary", FunctionTemplate::New (loadBinary));

		//	Go exploring for extensions...
		//	This needs to happen before we create a context for historical
		//	reasons which are not particularly compelling.

		PrepareExtensions (pathToMe.c_str ( ), extensions);

		//	Create a main script context and scope. We need only one of
		//	each because we're not threaded and we fork children so we
		//	don't have to clean up after scripts we run.

		Persistent <Context> context (Context::New (NULL, global));
		Context::Scope contextScope (context);

		// run bootstrap script and cope with aftermath

		Local <Value> bootstrapResult (ReadCompileRunScript ((pathToMe + ".ssjs").c_str ( )));
	
		if (bootstrapResult.IsEmpty ( ))		Throw (Exceptions::BootstrapScriptDied ( ));
		if (!bootstrapResult->IsBoolean ( ))	Throw (Exceptions::BootstrapScriptInsane ( ));
		if (bootstrapResult->IsFalse ( ))		Throw (Exceptions::BootstrapScriptFailed ( ));

		//	start accepting connections

		#define CLONE false

		AcceptLoop (CLONE, !geteuid ( ));
	}
	catch (exception & e)
	{
		syslog (LOG_ERR, "caught exception '%s' in main", e.what ( ));
		return 2;
	}
	catch (...)
	{
		syslog (LOG_ERR, "caught unknown exception in main");
		return 3;
	}

	syslog (LOG_NOTICE, "stopping gracefully");
	return 0;
}
